/*
 * Copyright 2018 KiWiPeach.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.java8.demo06_stream_api;

import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
import jdk.nashorn.internal.parser.JSONParser;
import jdk.nashorn.internal.runtime.JSONFunctions;
import org.junit.Before;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.BinaryOperator;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * Create Date: 2018/04/10
 * 代码参考文档：https://www.runoob.com/java/java8-streams.html
 * 代码参考文档：https://stackify.com/streams-guide-java-8/
 * Description: java8的Stream测试
 *
 * @author kiwipeach [1099501218@qq.com]
 */
public class RuNoobStreamTest {


    private List<Person> personList;

    @Before
    public void initData() {
        personList = Arrays.asList(
                new Person("1001", "张飞", 28, "男"),
                new Person("1002", "吕布", 21, "男"),
                new Person("1003", "刘备", 26, "男"),
                new Person("1004", "曹操", 20, "男"),
                new Person("1005", "貂蝉", 16, "女"),
                new Person("1006", "刘禅", 17, "男")
        );
    }


    /*############################################基础部分############################################*/

    /**
     * Stream API可以极大提高Java程序员的生产力，让程序员写出高效率、干净、简洁的代码。
     * 两个特别中澳的概念：
     * intermediate operation -- 中间操作（filter,sorted,map,skip,findFirst,anyMatch,distinct,flatmap,peek）
     * terminal operation     -- 最终操作（count,min,max,reduce,summaryStatistics,collect）
     * <p>
     * <p>
     * Intermediate operations such as filter() return a new stream on which further processing can be done.
     * Terminal operations, such as forEach(), mark the stream as consumed, after which point it can no longer be used further.
     * <p>
     * +--------------------+       +------+   +------+   +---+   +-------+
     * | stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
     * +--------------------+       +------+   +------+   +---+   +-------+
     */
    @Test
    public void 基础语法概念() {
        // 场景：过滤出大于等于20岁，对年龄进行排序，对姓名前加上“三国-”前缀，最后转换成一个key为id，value为Person对象的Map
        Map<String, Person> targetPersonList = personList.stream()
                // 过滤
                .filter(person -> person.getAge() >= 20)
                // 排序（默认降序排序）
                .sorted(Comparator.comparingInt(Person::getAge).reversed())
                // map映射修改
                .map(person -> {
                    person.setName(String.format("三国-%s", person.getName()));
                    return person;
                })
                // 转换成其他类型Map或者Set
                .collect(Collectors.toMap(person -> person.getId(), person -> person));
        // 打印输出
        targetPersonList.forEach((key, value) -> {
            System.out.println("key = " + key + "  value =" + value);
        });
        /**
         * key = 1004  value =Person{name='三国-曹操', age=20, sex=男}
         * key = 1003  value =Person{name='三国-刘备', age=26, sex=男}
         * key = 1002  value =Person{name='三国-吕布', age=21, sex=男}
         * key = 1001  value =Person{name='三国-张飞', age=28, sex=男}
         */
    }


    /*############################################拓展部分############################################*/
    @Test
    public void stream模拟分页操作() {
        // 场景：2个人1页，取第二页
        int pageNo = 2;
        int pageSize = 2;
        int skipIndex = pageSize * (pageNo - 1);
        personList.stream().skip(skipIndex).limit(pageSize).forEach(System.out::println);
    }

    @Test
    public void stream将Set转Map映射() {
        Map<String, Person> personMap = personList.stream().collect(Collectors.toMap(person -> person.getId(), person -> person));
        personMap.forEach((key, person) -> {
            System.out.println(key + " ---- " + person);
        });
        /**
         * 1006 ---- Person{name='刘禅', age=17, sex=男}
         * 1005 ---- Person{name='貂蝉', age=16, sex=女}
         * 1004 ---- Person{name='曹操', age=20, sex=男}
         * 1003 ---- Person{name='刘备', age=26, sex=男}
         * 1002 ---- Person{name='吕布', age=21, sex=男}
         * 1001 ---- Person{name='张飞', age=28, sex=男}
         */
    }

    @Test
    public void stream将Map转Set映射() {
        Map<String, Person> personMap = personList.stream().collect(Collectors.toMap(person -> person.getId(), person -> person));
        String namesStr = personMap.entrySet().stream()
                .map(personEntry -> personEntry.getValue().getName())
                .collect(Collectors.joining("-"));
        System.out.println(namesStr);
        /**
         * 刘禅-貂蝉-曹操-刘备-吕布-张飞
         */
    }

    class Student {
        public String id;
        public String name;
        public int age;

        @Override
        public String toString() {
            return "Student{" +
                    "id='" + id + '\'' +
                    ", name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

    @Test
    public void stream将A类型转B类型() {
        List<Student> students = personList.stream()
                .map(person -> {
                    Student stu = new Student();
                    stu.id = person.getId();
                    stu.name = person.getName();
                    stu.age = person.getAge();
                    return stu;
                }).collect(Collectors.toList());

        students.forEach(System.out::println);
    }

    @Test
    public void 通过builder创建Stream() {
        Stream.Builder<Person> empStreamBuilder = Stream.builder();
        empStreamBuilder.accept(personList.get(0));
        empStreamBuilder.accept(personList.get(1));
        empStreamBuilder.accept(personList.get(2));
        Stream<Person> personStream = empStreamBuilder.build();
        personStream.forEach(System.out::println);
    }

    @Test
    public void 通过flatMap进行符合嵌套集合平铺() {
        List<Person> personList01 = Arrays.asList(
                new Person("1001", "张飞", 28, "男"),
                new Person("1002", "吕布", 21, "男"),
                new Person("1003", "刘备", 26, "男")
        );
        List<Person> personList02 = Arrays.asList(
                new Person("1004", "曹操", 20, "男"),
                new Person("1005", "貂蝉", 16, "女"),
                new Person("1006", "刘禅", 17, "男")
        );

        // 嵌套List
        List<List<Person>> allPerson = Arrays.asList(personList01, personList02);
        // List<Person> flatPersonList = allPerson.stream().flatMap(personList -> personList.stream()).collect(Collectors.toList());
        List<Person> flatPersonList = allPerson.stream().flatMap(Collection::stream).collect(Collectors.toList());
        flatPersonList.stream().forEach(System.out::println);
    }

    /**
     * peek为在中断操作之前进行的函数操作，每一个元素的执行顺序为：peek操作先后顺序 > 中断操作（例如：foreach）
     */
    @Test
    public void 通过peek多次打印输出内容_它是中间操作比forEach更强_可用来调试中间操作的参数值() {
        System.out.println("begin：");
        // 返回stream说明不是中断操作
        Stream<Person> peekStream = personList.stream()
                .peek(person -> person.setAge(person.getAge() + 1))
                .peek(person -> System.out.println(person.getName() + " -- " + person.getAge()));

        // 注意：peek函数在中断前不会执行，只有在中断后才会执行。以下的collect和forEach都会中断操作
        // peekStream.collect(Collectors.toList());
        peekStream.forEach(System.out::println);

        System.out.println("end.");
    }

    @Test
    public void stream的数据源为集合时无需关闭_为IO流时需要手动关闭_但可以通过try_with_resoures对其自动关闭() throws IOException {
        try (Stream<String> fileStream = Files.lines(Paths.get("data.txt"))) {
            fileStream.sorted(Comparator.reverseOrder()).forEach(System.out::println);
        }
    }

    @Test
    public void 通过boxed将基础类型转成报装类型() {
        // 因为IntStream.range(1, 5)返回的类型不是包装类型的Stream，所以在collect(Collectors.toList())会报错
        // IntStream intStream = IntStream.range(1, 5);
        // List<Integer> collect = intStream.collect(Collectors.toList());
        Stream<Integer> intStream = Stream.of(1, 2, 3, 4, 5);
        intStream.collect(Collectors.toList()).forEach(System.out::println);
    }

    @Test
    public void 强大的Collectors() {
        // 字符拼接：Collectors.joining(",")
        String joinNames = personList.stream().map(person -> person.getName()).collect(Collectors.joining(","));
        System.out.println(joinNames);
        // 性别分组：Collectors.groupingBy(e->e.getName())
        Map<String, List<Person>> groupPersonList = personList.stream().collect(Collectors.groupingBy(person -> person.getSex()));
        groupPersonList.entrySet().stream().forEach(entry -> {
            System.out.println(entry.getKey() + "--" + entry.getValue());
        });
        // 性别统计：Collectors.groupingBy(Employee::getDept,Collectors.counting())
        Map<String, Long> countPersonMap = personList.stream().collect(Collectors.groupingBy(person -> person.getSex(), Collectors.counting()));
        countPersonMap.entrySet().stream().forEach(entry -> {
            System.out.println(entry.getKey() + "--" + entry.getValue());
        });

        // 性别统计后取最大年龄对象：
        Comparator<Person> byAge = Comparator.comparing(Person::getAge);
        Map<String, Optional<Person>> personMap2 = personList.stream().collect(Collectors.groupingBy(person -> person.getSex(), Collectors.reducing(BinaryOperator.maxBy(byAge))));
        personMap2.entrySet().stream().forEach(entry -> {
            System.out.println(entry.getKey() + "--" + entry.getValue());
        });


        /**
         *
         * 张飞,吕布,刘备,曹操,貂蝉,刘禅
         * 女--[Person{name='貂蝉', age=16, sex=女}]
         * 男--[Person{name='张飞', age=28, sex=男}, Person{name='吕布', age=21, sex=男}, Person{name='刘备', age=26, sex=男}, Person{name='曹操', age=20, sex=男}, Person{name='刘禅', age=17, sex=男}]
         * 女--1
         * 男--5
         * 女--Optional[Person{name='貂蝉', age=16, sex=女}]
         * 男--Optional[Person{name='张飞', age=28, sex=男}]
         */
    }

    @Test
    public void 强大的Collectors类型转换() {
        // toSet
        Set<String> nameSets = personList.stream()
                .map(Person::getName)
                .collect(Collectors.toSet());
        // toCollections,转换成任意java.util.stream.Collectors子类
        ArrayList<Integer> arrayList = personList.stream().map(Person::getAge).collect(Collectors.toCollection(ArrayList::new));
        arrayList.forEach(System.out::println);

    }

    static class RandomUtil {
        private static List<Integer> numbers = Arrays.asList(15, 34, 53, 37, 69);

        public static int getRandomNumber(int curNuber) {
            return numbers.get(new Random().nextInt(numbers.size()));
        }
    }

    @Test
    public void 无限流iterate() {
        // 可无限增长创建的流模型，所以一般会结合limit使用
        List<Integer> intList = Stream.iterate(10, n -> n + 3).limit(3).collect(Collectors.toList());
        intList.forEach((item) -> {
            System.out.print(item + " ");
        });
        // 从指定的数组范围内获取随机数
        Stream.iterate(RandomUtil.getRandomNumber(0), RandomUtil::getRandomNumber).limit(5).forEach(System.out::println);
    }

    @Test
    public void orElseThrow测试() {
        //personList.stream().max((p1, p2) -> p1.getAge() - p2.getAge());
        try {
            personList = new ArrayList<>();
            Optional<Person> maxOptional = personList.stream().max(Comparator.comparingInt(Person::getAge));
            if (maxOptional.isPresent()) {
                Person maxAgePerson = maxOptional.get();
                System.out.println("年龄最大的是:" + maxAgePerson);
            }
            // 如果最大值获取为空则抛出异常
            maxOptional.orElseThrow(() -> new NoSuchElementException("不存在最大元素"));
        } catch (NoSuchElementException e) {
            System.out.println(e.getMessage());
        }
    }


    @Test
    public void 文件写操作() {
        try (PrintWriter pw = new PrintWriter(
                Files.newBufferedWriter(Paths.get("employ.txt")))) {
            personList.stream().forEach(pw::println);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void 文件读操作() {
        try (Stream<String> lineStreaam = Files.lines(Paths.get("employ.txt"))) {
            lineStreaam.forEach(System.out::println);
        } catch (IOException e) {
            e.printStackTrace();
        }
        /**
         * Person{name='张飞', age=28, sex=男}
         * Person{name='吕布', age=21, sex=男}
         * Person{name='刘备', age=26, sex=男}
         * Person{name='曹操', age=20, sex=男}
         * Person{name='貂蝉', age=16, sex=女}
         * Person{name='刘禅', age=17, sex=男}
         */
    }


}
